﻿//======================================================================
//
//	Cross-Platform XNA Menu System
//  http://cpxms.codeplex.com/
//
//  All code and content licensed under Microsoft Public License (Ms-PL)	
//
//======================================================================
//
//	File:		MenuProject.cs
//
//	Author:		MJP
//
//	Date:		04/10/09
//
//======================================================================

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.ComponentModel;
using System.Collections.ObjectModel;
using System.Xml.Serialization;
using System.Drawing.Design;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace CPXMenu
{
	/// <summary>
	/// The MenuProject serves as the root item in the runtime hierarchy of pages and items.
	/// It manages a collection of pages, and and also manages addition and removal of items
	/// at design-time or runtime.
	/// </summary>
    [CanHaveChildren(true)]
    [IconKey("Project")]
    [ParentType(typeof(MenuProject))]
    [ChildType(typeof(Page))]
    public class MenuProject : MenuItem
    {
        private List<MenuItem> allMenuItems = new List<MenuItem>();
        private ulong nextID = 2;
        private MenuItem focusedItem = null;
        private SpriteBatch spriteBatch;
        private Page currentPage = null;
        private Page homePage = null;
		private ulong homePageID = 0;
        private MenuItem selectedItem = null;
        private RoundLineManager lineManager;
        private ImageState backgroundImage = new ImageState();
        private List<MenuItem> focusItems = new List<MenuItem>();
        private MenuInput lastInput = MenuInput.Empty;
        private Page prevCurrentPage = null;
        private List<MenuItem> emptyList = new List<MenuItem>();
        private DepthStencilBuffer dsBuffer = null;
        private bool cursorEnabled = false;
        private Point cursorSize = new Point(32, 32);
        private ImageState cursorImage = new ImageState();
        private List<ImportedContentDirectory> importedContentDirectories = new List<ImportedContentDirectory>();
        private List<ImportedContentItem> importedContent = new List<ImportedContentItem>();
        private ImportedContentDirectory importedContentRoot = new ImportedContentDirectory(ImportedContentDirectoryName, ImportedContentDirectoryName);

        private PageFlipEffect flipEffect;
        private Page flipFromPage;
        private Page flipToPage;
        private bool currentlyFlipping = false;
        private float flipTime = 0;
        private float currentFlipTime = 0;
        private PageFlipPostProcessor flipPostProcessor;

        public const ulong ProjectID = ulong.MaxValue;
        public const string DefaultCursorImage = "CPXMenu Content\\Textures\\Cursor";
        public const string ImportedContentDirectoryName = "Imported Content";

        /// <summary>
        /// Gets or sets the ImageState that describes the background image and color of the page
        /// </summary>
        [Browsable(true)]
        [Category("Appearance")]
        [Description("Describes the background color and image of the project")]        
        public ImageState BackgroundImage
        {
            get { return backgroundImage; }
            set 
            {
                backgroundImage = value;
                ImageStates.Clear();
                ImageStates.Add(backgroundImage);
            }
        }

		/// <summary>
		/// Gets a collection of all menu items in the project
		/// </summary>
        [Browsable(false)]
        [XmlIgnore]
        public ReadOnlyCollection<MenuItem> AllMenuItems
        {
            get { return allMenuItems.AsReadOnly(); }
        }

		/// <summary>
		/// Gets or sets the relative position of the project (always (0,0))
		/// </summary>
        [Browsable(false)]
        [XmlIgnore]
        public new Point Position
        {
            get { return new Point(0, 0); }
            set { }
        }

		/// <summary>
		/// Gets or sets the absolute position of the project (always (0,0))
		/// </summary>
        [Browsable(false)]
        [XmlIgnore]
        public override Point AbsolutePosition
        {
            get{ return new Point(0, 0); }
            set { }
        }

		/// <summary>
		/// Gets or sets the next ID to assign to an item
		/// </summary>
        [Browsable(false)]
        public ulong NextID
        {
            get { return nextID; }
            set { nextID = value; }
        }
		 
		/// <summary>
		/// Gets or sets the item that currently has focus
		/// </summary>
        [Browsable(false)]
        [XmlIgnore]
        public MenuItem FocusedItem
        {
            get { return focusedItem; }
            set
            {
                if (value == focusedItem)
                    return;

                if (value != null && !value.CanHaveFocus)
                {
                    Debug.Assert(false);
                    return;
                }

                if (value != null)
                    value.OnFocusAcquired();

                MenuItem oldItem = focusedItem;
                focusedItem = value;

                if (oldItem != null)
                    oldItem.OnFocusLost();

                OnFocusChanged(oldItem, focusedItem);
            }
        }

		/// <summary>
		/// Gets or sets the item that is currently selected
		/// </summary>
        [XmlIgnore]
        [Browsable(false)]
        public MenuItem SelectedItem
        {
            get { return selectedItem; }
            set
            {
                if (value == selectedItem)
                    return;

                if (value != null && !value.IsSelectable)
                {
                    Debug.Assert(false);
                    return;
                }

                if (value != null)
                    value.OnSelectionAcquired();

                MenuItem oldItem = selectedItem;
                selectedItem = value;

                if (oldItem != null)
                    oldItem.OnSelectionLost();

                OnSelectedItemChanged(oldItem, selectedItem);                
            }
        }

		/// <summary>
		/// Gets or sets the Page that is currently displayed
		/// </summary>
        [Browsable(true)]
        [Category("Runtime")]
        [Description("The page currently being displayed")]
        [EditorAttribute(typeof(PagePickerEditor), typeof(UITypeEditor))]
        [XmlIgnore]
        public Page CurrentPage
        {
            get { return currentPage; }
            set
            {
                if (value != currentPage)
                {
                    if (value == null || Children.Contains(value))
                    {
                        Page oldPage = currentPage;
                        currentPage = value;
                        prevCurrentPage = oldPage;

                        FocusedItem = null;
                        UpdateFocusItems();                        

                        OnCurrentPageChanged(oldPage);
                    }
                    else
                        Debug.Assert(false);
                }
            }
        }

		/// <summary>
		/// Gets or sets the home page of the project, which indicates
		/// the Page that should be displayed at startup
		/// </summary>
        [Browsable(true)]
        [Category("Runtime")]
        [Description("The page that the menu will start on")]
        [EditorAttribute(typeof(PagePickerEditor), typeof(UITypeEditor))]
        [XmlIgnore]
        public Page HomePage
        {
            get { return homePage; }
            set
            {
                if (value != homePage)
                {
                    if (value == null || Children.Contains(value))
                    {
                        Page oldPage = homePage;
                        homePage = value;
						if (homePage != null)
							homePageID = homePage.ID;
						else
							homePageID = 0;

                        OnHomePageChanged(oldPage);
                    }
                    else
                        Debug.Assert(false);
                }
            }
        }

        /// <summary>
        /// Gets or sets a value that controls drawing of the cursor
        /// </summary>
        [Browsable(true)]
        [Category("Cursor")]
        [Description("Enables or disables drawing of the cursor")]
        public bool CursorEnabled
        {
            get { return cursorEnabled; }
            set { cursorEnabled = value; }
        }

        /// <summary>
        /// Gets or sets a value that controls the size of the cursor
        /// </summary>
        [Browsable(true)]
        [Category("Cursor")]
        [Description("The size of the cursor image")]
        public Point CursorSize
        {
            get { return cursorSize; }
            set 
            {
                cursorSize = value;
                cursorSize.X = (int)MathHelper.Max(0, cursorSize.X);
                cursorSize.Y = (int)MathHelper.Max(0, cursorSize.Y);
            }
        }

        /// <summary>
        /// Gets or sets the image drawn for the cursor
        /// </summary>
        [Browsable(true)]
        [Category("Cursor")]
        [Description("The image drawn for the cursor")]
        public ImageState CursorImage
        {
            get { return cursorImage; }
            set { cursorImage = value; }
        }

		/// <summary>
		/// Gets or sets the Index of the item (always 0)
		/// </summary>
        [Browsable(false)]
        [XmlIgnore]
        public override int Index
        {
            get
            {
                return 0;
            }
            set
            {
            }
        }

		/// <summary>
		/// Gets or sets the ID of the home page
		/// </summary>
        [Browsable(false)]
        public ulong HomePageID
        {
			get { return homePageID; }
            set
            {
				this.homePageID = value;
            }
        }

		/// <summary>
		/// Gets a collection of all Pages in the project
		/// </summary>
        [Browsable(false)]
        [XmlIgnore]
        public ReadOnlyCollection<Page> MenuPages
        {
            get
            {
                List<Page> pages = new List<Page>();
                foreach (MenuItem item in Children)
                {
                    Page page = item as Page;
                    if (page != null)
                        pages.Add(page);
                    else
                        Debug.Assert(false);
                }
                return pages.AsReadOnly();
            }                
        }

        /// <summary>
        /// Gets a collection of ImportedContentItems for the project
        /// </summary>
        [Browsable(false)]
        [XmlIgnore]
        public ReadOnlyCollection<ImportedContentItem> ImportedContent
        {
            get { return importedContent.AsReadOnly(); }
        }

        /// <summary>
        /// Gets a collection of ImportedContentDirectory's for the project
        /// </summary>
        [Browsable(false)]
        [XmlIgnore]
        public ReadOnlyCollection<ImportedContentDirectory> ImportedContentDirectories
        {
            get { return importedContentDirectories.AsReadOnly(); }
        }

        [Browsable(false)]
        public ImportedContentDirectory ImportedContentRoot
        {
            get { return importedContentRoot; }
            set { importedContentRoot = value; }
        }

		/// <summary>
		/// Creates a new MenuProject instance
		/// </summary>
        public MenuProject()
            : base()
        {
            BackgroundImage = backgroundImage;
            ImageStateIndex = 0;
            ID = ProjectID;

#if WINDOWS
            cursorImage.TextureName = DefaultCursorImage;
            cursorEnabled = true;
#endif
        }

		/// <summary>
		/// Gets a MenuItem by Name
		/// </summary>
		/// <typeparam name="T">The MenuItem Type to find</typeparam>
		/// <param name="name">The Name of the item to find</param>
		/// <returns>The MenuItem whose name matches the specifed name string, or null if none match</returns>
        public T GetMenuItemByName<T>(string name) where T : MenuItem
        {
            foreach (MenuItem item in allMenuItems)
                if (item.Name == name)
                    return item as T;
            return null;
        }

		/// <summary>
		/// Gets a MenuItem by ID
		/// </summary>
		/// <typeparam name="T">The MenuItem Type to find</typeparam>
		/// <param name="id">The ID of the item to find</param>
		/// <returns>The MenuItem whose ID matches the specifed id, or null if none match</returns>
        public T GetMenuItemByID<T>(ulong id) where T : MenuItem
        {
            Debug.Assert(id != 0);

            if (id == ProjectID)
                return this as T;

            foreach (MenuItem item in allMenuItems)
                if (item.ID == id)
                    return item as T;
            return null;
        }        

		/// <summary>
		/// Updates the project as well as all child items.  Should be
		/// called once per frame.
		/// </summary>
		/// <param name="input">The MenuInput struct containing input data</param>
		/// <param name="dt">The time delta</param>
        public void UpdateProject(MenuInput input, float dt)
        {
            // Don't process input while we're page flipping
            if (currentlyFlipping)
            {
                lastInput = input;
                return;
            }

            int focusedItemIndex = -1;
            if (focusedItem != null)
            {
                for (int i = 0; i < focusItems.Count; i++)
                    if (focusedItem == focusItems[i])            
                        focusedItemIndex = i;
                Debug.Assert(focusedItemIndex != -1);
            }

            // Check for cursor movement
            if (input.CursorPos.X != lastInput.CursorPos.X || input.CursorPos.Y != lastInput.CursorPos.Y)
            {
                MenuItem hoverItem = ClickTest(input.CursorPos);
                if (hoverItem != null && hoverItem.CanHaveFocus)
                    FocusedItem = hoverItem;
                else
                    FocusedItem = null;

            }
            else
            {
                if (focusItems.Count > 0)
                {
                    bool next = false, previous = false;
                    next = ((input.Right && !lastInput.Right) || (input.Down && !lastInput.Down));
                    previous = ((input.Left && !lastInput.Left) || (input.Up && !lastInput.Up));

                    // Check for dpad/keyboard movement
                    if (next)
                    {
                        focusedItemIndex = (focusedItemIndex + 1) % focusItems.Count;
                        FocusedItem = focusItems[focusedItemIndex];
                    }
                    else if (previous)
                    {
                        focusedItemIndex--;
                        if (focusedItemIndex < 0)
                            focusedItemIndex = focusItems.Count - 1;
                        FocusedItem = focusItems[focusedItemIndex];
                    }
                }
            }

            // Check for selection
            if (input.Select && FocusedItem != null)
                SelectedItem = FocusedItem;
            else
                SelectedItem = null;

            lastInput = input;


            Update(dt);
        }

        /// <summary>
        /// Renders the project
        /// </summary>
        /// <param name="dt">The time delta, in seconds</param>
        public void RenderProject(float dt)
        {
            RenderProject(dt, emptyList.AsReadOnly(), null);
        }

		/// <summary>
		/// Renders the project as well as all child items
		/// </summary>
		/// <param name="dt">The time delta, in seconds.</param>
		/// <param name="selectedItems">A collection of items that are currently selected (design-time only)</param>
		/// <param name="highlightedItem">The currently highlighted item (design-time only)</param>
        public void RenderProject(float dt, ReadOnlyCollection<MenuItem> selectedItems, MenuItem highlightedItem)
        {
            // DepthStencilBuffer originalDS = graphicsDevice.DepthStencilBuffer;
            // graphicsDevice.DepthStencilBuffer = dsBuffer;

            graphicsDevice.Clear(ClearOptions.Target, Color.TransparentBlack, 1, 0);

            graphicsDevice.RenderState.ScissorTestEnable = true;

            if (DesignMode)
                dt = 0;

            Render(dt, spriteBatch, lineManager, selectedItems, highlightedItem);

            graphicsDevice.RenderState.ScissorTestEnable = false;   

            // graphicsDevice.DepthStencilBuffer = originalDS;
        }

		/// <summary>
		/// Initializes the project
		/// </summary>
		/// <param name="services">The IServiceProvider through which the GraphicsDeviceService can be found</param>
        /// <param name="contentRootDirectory">The root directory where content can be found</param>
        public void Initialize(IServiceProvider services, string contentRootDirectory)
        {
            this.contentManager = new ContentManager(services, contentRootDirectory);

            this.Initialize(contentManager);
        }

        /// <summary>
        /// Initializes the project
        /// </summary>
        /// <param name="contentManager">The ContentManager to use for loading content</param>
        public void Initialize(ContentManager contentManager)
        {
            this.contentManager = contentManager;

            IGraphicsDeviceService service = (IGraphicsDeviceService)contentManager.ServiceProvider.GetService(typeof(IGraphicsDeviceService));

            base.Initialize(this, contentManager, service.GraphicsDevice);

            if (cursorImage != null)
                cursorImage.Initialize(contentManager, this);

            spriteBatch = new SpriteBatch(graphicsDevice);
            lineManager = new RoundLineManager();
            lineManager.Init(graphicsDevice, contentManager);

            AddToImportedContentDirectories(importedContentRoot);
            importedContentRoot.SetParentDirectory(null);

            foreach (MenuItem item in Children)            
                AddToAllItems(item);
            foreach (MenuItem item in Children)            
                InitializeItem(item);            

			if (homePageID != 0)
				HomePage = GetMenuItemByID<Page>(homePageID);

			if (homePage == null && MenuPages.Count > 0)
				HomePage = MenuPages[0];

			CurrentPage = HomePage;

            UpdateFocusItems();

            flipPostProcessor = new PageFlipPostProcessor(graphicsDevice, contentManager);

            CreateDSBuffer();
        }

		/// <summary>
		/// Adds a new item of the specified Type to the project
		/// </summary>
		/// <param name="itemType">The Type of MenuItem to create</param>
		/// <param name="parent">The parent to add the item to</param>
		/// <returns>The created MenuItem</returns>
        public MenuItem AddNewItemToProject(Type itemType, MenuItem parent)
        {
            Debug.Assert(MenuItem.IsMenuItemType(itemType));
            MenuItem newItem = (MenuItem)Activator.CreateInstance(itemType);
            int x = (int)((parent.Dimensions.X / 2.0f) - (newItem.Dimensions.X / 2.0f));
            int y = (int)((parent.Dimensions.Y / 2.0f) - (newItem.Dimensions.Y / 2.0f));
            newItem.Position = new Point(x, y);
            newItem.DesignMode = DesignMode;
            AddToProject(parent, newItem);
            return newItem;
        }

		/// <summary>
		/// Adds an already-created item to the project. The item will be initialized,
		/// and assigned a unique Name and ID.
		/// </summary>
		/// <param name="parent">The parent item to which the item should be added</param>
		/// <param name="child">The child item that is being added to the project</param>
        public void AddToProject(MenuItem parent, MenuItem child)
        {
            Debug.Assert(!allMenuItems.Contains(child));
            
            AssignID(child);
            parent.AddChild(child);
            AddToAllItems(child);
            CallOnAddedToProject(child);
            AssignUniqueName(child);

			try
			{
				InitializeItem(child);
			}
			catch (ContentLoadException e)
			{
				RemoveFromAllItems(child);
				parent.RemoveChild(child);
				throw e;
			}
            
			OnItemAdded(parent, child);

            UpdateFocusItems();
        }

		/// <summary>
		/// Adds an array of already-created items to the project. The items will be
		/// initialized and assigned unique Names and IDs
		/// </summary>
		/// <param name="parents"></param>
		/// <param name="children"></param>
        public void AddToProject(MenuItem[] parents, MenuItem[] children)
        {
            Debug.Assert(parents.Length == children.Length);

            for (int i = 0; i < parents.Length; i++)
                AddToProject(parents[i], children[i]);
        }

		/// <summary>
		/// Removes an item from the project
		/// </summary>
		/// <param name="item"></param>
        public void RemoveFromProject(MenuItem item)
        {
            Debug.Assert(allMenuItems.Contains(item));

            MenuItem parent = item.Parent;
            CallOnRemovedFromProject(item);
            item.Parent.RemoveChild(item);
            RemoveFromAllItems(item);

            if (FocusedItem == item)
                FocusedItem = null;
            if (SelectedItem == item)
                SelectedItem = null;

            OnItemRemoved(parent, item);

            UpdateFocusItems();
        }

		/// <summary>
		/// Removes an array of items from the project
		/// </summary>
		/// <param name="items"></param>
        public void RemoveFromProject(MenuItem[] items)
        {
            for (int i = 0; i < items.Length; i++)
                RemoveFromProject(items[i]);
        }		

		/// <summary>
		/// Tests whether the specified cursor position intersects with any items
		/// </summary>
		/// <param name="cursorPos">The position of the cursor</param>
		/// <returns>The top-most item that the cursor intersects, or null if it doesn't intersect.</returns>
        public override MenuItem ClickTest(Point cursorPos)
        {
            if (currentPage != null)
                return currentPage.ClickTest(cursorPos);
            else
                return null;
        }

		/// <summary>
		/// Enables or disables DesignMode for the project
		/// </summary>
		/// <param name="enable"></param>
        public void ToggleDesignMode(bool enable)
        {
            DesignMode = enable;
            foreach (MenuItem item in allMenuItems)
                item.DesignMode = enable;
        }

        /// <summary>
        /// Adds an imported content item to the project
        /// </summary>
        /// <param name="content">The content item to add</param>
        public void AddImportedContent(ImportedContentItem content, ImportedContentDirectory parentDirectory)
        {
            if (importedContent.Contains(content))
            {
                Debug.Assert(false);
                return;
            }

            if (parentDirectory.Content.Contains(content))
            {
                Debug.Assert(false);
                return;
            }

            importedContent.Add(content);
            parentDirectory.Content.Add(content);
            content.SetParentDirectory(parentDirectory);
            OnImportedContentAdded(content);
        }

        /// <summary>
        /// Removes an imported content item from the project
        /// </summary>
        /// <param name="content">The item to remove</param>
        public void RemoveImportedContent(ImportedContentItem content)
        {
            if (!importedContent.Contains(content))
            {
                Debug.Assert(false);
                return;
            }

            importedContent.Remove(content);
            content.ParentDirectory.Content.Remove(content);
            content.SetParentDirectory(null);
            OnImportedContentRemoved(content);
        }

        /// <summary>
        /// Adds a content directory to the project
        /// </summary>
        /// <param name="directory">The directory to add</param>
        public void AddImportedContentDirectory(ImportedContentDirectory directory, ImportedContentDirectory parentDirectory)
        {
            if (importedContentDirectories.Contains(directory))
            {
                Debug.Assert(false);
                return;
            }

            if (parentDirectory.Directories.Contains(directory))
            {
                Debug.Assert(false);
                return;
            }

            parentDirectory.Directories.Add(directory);
            directory.SetParentDirectory(parentDirectory);
            AddToImportedContentDirectories(directory);

            OnImportedContentDirectoryAdded(directory);
        }

        /// <summary>
        /// Removes the content directory from the project
        /// </summary>
        /// <param name="directory"></param>
        public void RemoveImportedContentDirectory(ImportedContentDirectory directory)
        {
            if (!importedContentDirectories.Contains(directory))
            {
                Debug.Assert(false);
                return;
            }

            importedContentDirectories.Remove(directory);
            OnImportedContentDirectoryRemoved(directory);
        }

        /// <summary>
        /// Adds a child item to the children collection
        /// </summary>
        /// <param name="child">The child to add</param>
        internal override void AddChild(MenuItem child)
        {
            base.AddChild(child);

            // Should only be adding pages to a project...
            Debug.Assert(child is Page);

            if (homePage == null && MenuPages.Count > 0)
                HomePage = MenuPages[0];

            CurrentPage = HomePage;            
        }

        /// <summary>
        /// Removes an item that is a direct child of the project.
        /// </summary>
        /// <param name="child"></param>
        internal override void RemoveChild(MenuItem child)
        {
            base.RemoveChild(child);

            if (child == currentPage)
            {
                if (MenuPages.Count > 0)
                    CurrentPage = MenuPages[0];
                else
                    CurrentPage = null;
            }

            if (child == HomePage)
            {
                if (MenuPages.Count > 0)
                    HomePage = MenuPages[0];
                else
                    HomePage = null;
            }

        }

		/// <summary>
		/// Renders the item and all children
		/// </summary>
		/// <param name="dt">The time delta</param>
		/// <param name="spriteBatch">The shared SpriteBatch instance</param>
		/// <param name="lineManager">The shared RoundLineManager instance</param>
		/// <param name="selectedItems">The List of selected items</param>
		/// <param name="highlightedItem">The currently highlighted item</param>
		internal override void Render(float dt, SpriteBatch spriteBatch, RoundLineManager lineManager, ReadOnlyCollection<MenuItem> selectedItems, MenuItem highlightedItem)
		{
			if (currentlyFlipping)
				DoPageFlip(dt, spriteBatch, lineManager);
			else
				base.Render(dt, spriteBatch, lineManager, selectedItems, highlightedItem);

            if (!DesignMode && cursorEnabled && cursorImage != null)
            {
                Rectangle drawRect = new Rectangle(lastInput.CursorPos.X, lastInput.CursorPos.Y, cursorSize.X, cursorSize.Y);
                cursorImage.Render(dt, spriteBatch, drawRect, 1.0f);
            }

		}

        /// <summary>
        /// Renders the current page
        /// </summary>
        /// <param name="dt">Time delta</param>
        /// <param name="spriteBatch">Shared SpriteBatch</param>
        /// <param name="lineManager">Shared RoundLineManager</param>
        /// <param name="selectedItems">Collection of selected items (design mode only)</param>
        /// <param name="highlightedItem">Highlighted item (design mode only)</param>
        internal override void RenderChildren(float dt, SpriteBatch spriteBatch, RoundLineManager lineManager, ReadOnlyCollection<MenuItem> selectedItems, MenuItem highlightedItem)
        {
			if (currentPage != null)
                currentPage.Render(dt, spriteBatch, lineManager, selectedItems, highlightedItem);
        }

		/// <summary>
		/// Starts off the page flip and initializes the PageFlipEffect
		/// </summary>
		/// <param name="type">The type of PageFlip to begin</param>
		/// <param name="effect">The PageFlipEffect to use</param>
		/// <param name="targetPage">The target of page to flip to</param>
        internal void StartPageFlip(PageFlipType type, PageFlipEffect effect, Page targetPage)
        {
            if (type == PageFlipType.None)
                return;
            else if (type == PageFlipType.Return)
                if (prevCurrentPage == null)
                    return;
                else
                    targetPage = prevCurrentPage;

            if (targetPage == null)
                return;

            if (effect is InstantPageFlipEffect)
            {
                // Well That was easy!
                CurrentPage = targetPage;
                return;                
            }

            flipTime = effect.FlipTime;
            currentFlipTime = 0;

            currentlyFlipping = true;
            flipToPage = targetPage;
            flipFromPage = CurrentPage;            
            flipEffect = effect;            
        }

		/// <summary>
		/// Called when CurrentPage changes. Raises The CurrentPageChanged event.
		/// </summary>
		/// <param name="oldPage">The previous CurrentPage</param>
        protected virtual void OnCurrentPageChanged(Page oldPage)
        {
            if (CurrentPageChanged != null)
                CurrentPageChanged(this, oldPage, currentPage);
        }

		/// <summary>
		/// Called when HomePage changes. Raises the HomePageChanged event.
		/// </summary>
		/// <param name="oldPage"></param>
        protected virtual void OnHomePageChanged(Page oldPage)
        {
            if (HomePageChanged != null)
                HomePageChanged(this, oldPage, homePage);
        }		

		/// <summary>
		/// Called when the dimensions of the project are changed
		/// </summary>
        protected override void OnDimensionsChanged()
        {
            base.OnDimensionsChanged();

            // Set page dimensions
            foreach (MenuItem child in Children)
                child.Dimensions = Dimensions;

            ClearPageFlipRenderTargets();
            CreateDSBuffer();
        }

		/// <summary>
		/// Called when FocusedItem changes.  Raises the FocusChanged event.
		/// </summary>
		/// <param name="oldItem">The previous FocusedItem</param>
		/// <param name="newItem">The current FocusedItem</param>
        protected virtual void OnFocusChanged(MenuItem oldItem, MenuItem newItem)
        {
            if (FocusChanged != null)
                FocusChanged(this, oldItem, newItem);
        }

		/// <summary>
		/// Called when SelectedItem changes.  Raises the SelectedItemChanged event.
		/// </summary>
		/// <param name="oldItem">The previous SelectedItem</param>
		/// <param name="newItem">The current SelectedItem</param>
        protected virtual void OnSelectedItemChanged(MenuItem oldItem, MenuItem newItem)
        {
            if (SelectedItemChanged != null)
                SelectedItemChanged(this, oldItem, newItem);
        }

		/// <summary>
		/// Called when an item is added to the project. Raises the ItemAdded event.
		/// </summary>
		/// <param name="parent">The item that the new item was added to</param>
		/// <param name="menuItem">The new item</param>
        protected virtual void OnItemAdded(MenuItem parent, MenuItem menuItem)
        {
            if (ItemAdded != null)
                ItemAdded(this, parent, menuItem);

            foreach (MenuItem child in menuItem.Children)
                OnItemAdded(menuItem, child);
        }

		/// <summary>
		/// Called when an item is removed.  Raises the ItemRemoved event.
		/// </summary>
		/// <param name="parent">The parent of the removed item</param>
		/// <param name="menuItem">The removed item</param>
        protected virtual void OnItemRemoved(MenuItem parent, MenuItem menuItem)
        {
            if (ItemRemoved != null)
                ItemRemoved(this, parent, menuItem);

            foreach (MenuItem child in menuItem.Children)
                OnItemRemoved(menuItem, child);
        }

        /// <summary>
        /// Called when an ImportedContentItem is added to the project
        /// </summary>
        /// <param name="contentItem">The imported content that was added</param>
        protected virtual void OnImportedContentAdded(ImportedContentItem contentItem)
        {
            if (ImportedContentAdded != null)
                ImportedContentAdded(this, contentItem);
        }

        /// <summary>
        /// Called when an ImportedContentItem is removed from the project
        /// </summary>
        /// <param name="contentItem">The imported content that was removed</param>
        protected virtual void OnImportedContentRemoved(ImportedContentItem contentItem)
        {
            if (ImportedContentRemoved != null)
                ImportedContentRemoved(this, contentItem);

            foreach (ulong id in contentItem.OwnerIDs)
                GetMenuItemByID<MenuItem>(id).OnOwnedContentRemoved(contentItem);
        }

        /// <summary>
        /// Called when an ImportedContentDirectory is added to the project
        /// </summary>
        /// <param name="contentItem">The imported content directory that was added</param>
        protected virtual void OnImportedContentDirectoryAdded(ImportedContentDirectory contentDirectory)
        {
            if (ImportedContentDirectoryAdded != null)
                ImportedContentDirectoryAdded(this, contentDirectory);
        }

        /// <summary>
        /// Called when an ImportedContentDirectory is removed from the project
        /// </summary>
        /// <param name="contentItem">The imported content directory that was removed</param>
        protected virtual void OnImportedContentDirectoryRemoved(ImportedContentDirectory contentDirectory)
        {
            if (ImportedContentDirectoryRemoved != null)
                ImportedContentDirectoryRemoved(this, contentDirectory);

        }

		/// <summary>
		/// Assigns a unique name to an item and all children
		/// </summary>
		/// <param name="menuItem">The item whose Name will be assigned</param>
        private void AssignUniqueName(MenuItem menuItem)
        {
            uint count = 1;
            string baseName = menuItem.Name;
            string name = menuItem.Name;
            MenuItem foundItem = GetMenuItemByName<MenuItem>(name);

            if (foundItem != null && foundItem != menuItem)
            {
				// Try to figure out if this item still has its default name, or 
				// a default name plus a number.  In this case we'll just increment the
				// number rather than tacking on the number (otherwise you go from "Button1" to "Button11")
                string typeName = menuItem.GetType().Name;
                if (name.Length > typeName.Length && name.Substring(0, typeName.Length) == typeName)
                {
                    string endPart = name.Substring(typeName.Length);
                    try
                    {
                        count = uint.Parse(endPart);
                        baseName = typeName;
                    }
                    catch (FormatException)
                    {
                    }
                }
            }

            while (foundItem != null && foundItem != menuItem)
            {
                name = baseName + count.ToString();
                foundItem = GetMenuItemByName<MenuItem>(name);
                count++;
            }
            menuItem.Name = name;

            foreach (MenuItem child in menuItem.Children)
                AssignUniqueName(child);
        }

		/// <summary>
		/// Assigns the item and all child items an ID
		/// </summary>
		/// <param name="menuItem">The item whose ID will be assigned</param>
        private void AssignID(MenuItem menuItem)
        {            
            menuItem.ID = nextID;
            nextID++;

            foreach (MenuItem child in menuItem.Children)
                AssignID(child);
        }

		/// <summary>
		/// Initializes an item and all child items
		/// </summary>
		/// <param name="menuItem">The item to initialize</param>
        private void InitializeItem(MenuItem menuItem)
        {
            menuItem.Initialize(this, contentManager, graphicsDevice);
            menuItem.DesignMode = this.DesignMode;

            foreach (MenuItem child in menuItem.Children)
                InitializeItem(child);
        }

		/// <summary>
		/// Calls "OnAddedToProject" for the item and all children
		/// </summary>
		/// <param name="menuItem">The item on which OnAddedToProject will be called</param>
        private void CallOnAddedToProject(MenuItem menuItem)
        {
            menuItem.OnAddedToProject();

            foreach (MenuItem child in menuItem.Children)
                CallOnAddedToProject(child);
        }

		/// <summary>
		/// Calls "OnRemovedFromProject" for the item and all children
		/// </summary>
		/// <param name="menuItem">The item on which OnRemovedToProject will be called</param>
        private void CallOnRemovedFromProject(MenuItem menuItem)
        {
            menuItem.OnRemovedFromProject();

            foreach (MenuItem child in menuItem.Children)
                CallOnRemovedFromProject(child);
        }

		/// <summary>
		/// Adds the item and all children to the AllItems collection
		/// </summary>
		/// <param name="menuItem">The item to add</param>
        private void AddToAllItems(MenuItem menuItem)
        {
            allMenuItems.Add(menuItem);

            foreach (MenuItem child in menuItem.Children)
                AddToAllItems(child);
        }

        private void AddToImportedContentDirectories(ImportedContentDirectory directory)
        {
            importedContentDirectories.Add(directory);

            foreach (ImportedContentItem contentItem in directory.Content)
                importedContent.Add(contentItem);

            foreach (ImportedContentDirectory subDirectory in directory.Directories)
                AddToImportedContentDirectories(subDirectory);
        }

        private void RemoveFromImportedContentDirectories(ImportedContentDirectory directory)
        {
            importedContentDirectories.Add(directory);

            foreach (ImportedContentItem contentItem in directory.Content)
            {
                importedContent.Add(contentItem);
                foreach(ulong id in contentItem.OwnerIDs)
                    GetMenuItemByID<MenuItem>(id).OnOwnedContentRemoved(contentItem);
            }

            foreach (ImportedContentDirectory subDirectory in directory.Directories)
                RemoveFromImportedContentDirectories(subDirectory);
        }

		/// <summary>
		/// Removes the item and all children from the AllItems collection
		/// </summary>
		/// <param name="menuItem">The item to remove</param>
        private void RemoveFromAllItems(MenuItem menuItem)
        {
            allMenuItems.Remove(menuItem);

            foreach (MenuItem child in menuItem.Children)
                RemoveFromAllItems(child);
        }

        /// <summary>
        /// Adds all items in the project that can have focus to the focusItems List
        /// </summary>
        private void UpdateFocusItems()
        {
            focusItems.Clear();
            if (currentPage != null)
                UpdateFocusItems(currentPage.Children);
        }

        /// <summary>
        /// Adds all items in the specified List that can have focus to the focusItems list
        /// </summary>
        /// <param name="items">The List of items to check</param>
        private void UpdateFocusItems(List<MenuItem> items)
        {
            for (int i = 0; i < items.Count; i++)
            {
                if (items[i].CanHaveFocus)
                    focusItems.Add(items[i]);
                UpdateFocusItems(items[i].Children);                
            }
        }

        /// <summary>
        /// Clears the collection of render targets used for page flipping
        /// </summary>
        private void ClearPageFlipRenderTargets()
        {
            if (this.flipPostProcessor == null)
                return;

            flipPostProcessor.ClearRenderTargets();
        }

		/// <summary>
		/// Creates the depth stencil buffer used by the project
		/// </summary>
        private void CreateDSBuffer()
        {
            if (graphicsDevice == null)
                return;

            DepthFormat format = DepthFormat.Depth24Stencil8;
            if (graphicsDevice.DepthStencilBuffer != null)
                format = graphicsDevice.DepthStencilBuffer.Format;
            dsBuffer = new DepthStencilBuffer(graphicsDevice, Dimensions.X, Dimensions.Y, format);
        }

		/// <summary>
		/// Renders the currently active PageFlipEffect
		/// </summary>
		/// <param name="dt">Time delta</param>
		/// <param name="spriteBatch">Shared SpriteBatch instance</param>
		/// <param name="lineManager">shared RoundLineManager instance</param>
        private void DoPageFlip(float dt, SpriteBatch spriteBatch, RoundLineManager lineManager)
        {
            flipEffect.Render(dt, currentFlipTime, graphicsDevice, spriteBatch, lineManager, flipFromPage, flipToPage, flipPostProcessor);

            currentFlipTime += dt;

            if (currentFlipTime >= flipTime)
            {
                flipTime = 0;
                currentFlipTime = 0;
                currentlyFlipping = false;
                flipEffect = null;
                CurrentPage = flipToPage;
            }

            flipPostProcessor.ReleaseAllRenderTargets();
        }

		/// <summary>
		/// Gets an array of all Type's needed for XML serialization or deserialization
		/// of a MenuProject.
		/// </summary>
		/// <returns>An array all Type's needed for XML serialization or deserialization</returns>
        public static Type[] GetSerializationTypes()
        {
            List<Type> serializationTypes = new List<Type>();
            serializationTypes.AddRange(MenuItem.GetMenuItemTypes());
            serializationTypes.AddRange(PageFlipEffect.GetPageFlipEffectTypes());
            return serializationTypes.ToArray();
        }

        /// <summary>
        /// Called when an item is added to the project
        /// </summary>
        public event ItemAddedOrRemovedDelegate ItemAdded;

		/// <summary>
		/// Called when an item is removed from the project
		/// </summary>
        public event ItemAddedOrRemovedDelegate ItemRemoved;

		/// <summary>
		/// Called when the CurrentPage is changed
		/// </summary>
        public event PageChangedDelegate CurrentPageChanged;

		/// <summary>
		/// Called when the HomePage is changed
		/// </summary>
        public event PageChangedDelegate HomePageChanged;

		/// <summary>
		/// Called when FocusedItem changes
		/// </summary>
        public event ItemChangedDelegate FocusChanged;

		/// <summary>
		/// Called when SelectedItem changes
		/// </summary>
        public event ItemChangedDelegate SelectedItemChanged;

        /// <summary>
        /// Called when a new ImportedContentItem is added to the project
        /// </summary>
        public event ContentAddedOrRemovedDelegate ImportedContentAdded;

        /// <summary>
        /// Called when an ImportedContentItem is removed from the project
        /// </summary>
        public event ContentAddedOrRemovedDelegate ImportedContentRemoved;

        /// <summary>
        /// Called when a new ImportedContentDirectory is added to the project
        /// </summary>
        public event ContentDirectoryAddedOrRemovedDelegate ImportedContentDirectoryAdded;

        /// <summary>
        /// Called when an ImportedContentDirectory is removed from the project
        /// </summary>
        public event ContentDirectoryAddedOrRemovedDelegate ImportedContentDirectoryRemoved;
    }

    public delegate void ItemAddedOrRemovedDelegate(MenuProject project, MenuItem parent, MenuItem item);
    public delegate void PageChangedDelegate(MenuProject project, Page oldPage, Page newPage);
    public delegate void ItemChangedDelegate(MenuProject project, MenuItem oldItem, MenuItem newItem);
    public delegate void ContentAddedOrRemovedDelegate(MenuProject project, ImportedContentItem contentItem);
    public delegate void ContentDirectoryAddedOrRemovedDelegate(MenuProject project, ImportedContentDirectory contentDirectory);
}